﻿using System;
using System.IO;
using System.Xml;
using System.Reflection;
using System.Diagnostics;
using System.Collections.Generic;
using System.Management;

namespace Heaven.Diagnostics
{
    /// <summary>
    /// Сборка, подверженная тестированию
    /// </summary>
    public class DiagnosedAssembly
    {
        #region Аттрибуты

        // Название сборки
        private string name = null;
        // UNC путь к сборке
        private string location = null;
        // Тесты, содержащиеся в сборке
        private List<Test> tests = new List<Test>();

        #endregion

        #region Свойства

        /// <summary>
        /// Название сборки (только чтение)
        /// </summary>
        /// <value>Строка, название сборки</value>
        public string Name
        {
            get { return name; }
        }

        /// <summary>
        /// UNC путь к сборке
        /// </summary>
        public string Location
        {
            get { return location; }
        }

        /// <summary>
        /// Найденые в сборке тесты
        /// </summary>
        /// <value></value>
        public List<Test> Tests
        {
            get { return tests; }
        }

        #endregion

        #region Методы

        /// <summary>
        /// Загрузить сборку
        /// </summary>
        /// <param name="assembly">Сборка</param>
        /// <returns>True, если удалось загрузить, иначе false</returns>
        public static DiagnosedAssembly Load(Assembly assembly)
        {
            // Проверка, удовлитворяет ли сборка спецификации
            if (!IsCompatibleAssembly(assembly))
            {
                throw new Exception(
                    "Не возможно загрузить сборку " + assembly.FullName +
                    ", т.к. она не удовлитворяет спецификации");
            }

            DiagnosedAssembly result = new DiagnosedAssembly();

            // Запоминаем путь до сборки
            result.location = assembly.Location;
            
            // Находим атрибут на уровне сборки, чтобы узнать 
            // название сборки для данной тестирующей программы
            object[] attrs = assembly.GetCustomAttributes(typeof(DiagnosticAssemblyAttribute), true);
            result.name = ((DiagnosticAssemblyAttribute)attrs[0]).Title;

            // Ищем тестирующие классы
            Type[] types = assembly.GetTypes();
            foreach (Type t in types)
            {
                // Это должен быть класс
                if (!t.IsClass) continue;

                // Класс должен содержать специальный атрибут
                attrs = t.GetCustomAttributes(typeof(DiagnosticClassAttribute), true);
                if (attrs.Length == 0) continue;
                
                // Создаем экземпляр класса
                string groupname = ((DiagnosticClassAttribute)attrs[0]).Title;
                Object obj = Activator.CreateInstance(t);
                MethodInfo[] mi = t.GetMethods();

                foreach (MethodInfo m in mi)
                {
                    // Ищем среди методов тесты
                    attrs = m.GetCustomAttributes(typeof(DiagnosticMethodAttribute), true);
                    if (attrs.Length != 0)
                    {
                        string methodname = ((DiagnosticMethodAttribute)attrs[0]).Title;
                        result.tests.Add(new Test(groupname, methodname, obj, m));
                    }
                }                
            }

            return result;
        }

        /// <summary>
        /// Запись результатов в IO поток.
        /// Формат XML, подробнее см. документацию
        /// </summary>
        /// <param name="stream">Поток для записи</param>
        public void SaveReport(Stream stream)
        {           
            XmlWriter writer = new XmlTextWriter(stream, null);

            writer.WriteStartDocument(true);
            writer.WriteStartElement("DiagnosticReport");
            writer.WriteAttributeString("Version", "1");

            // Название сборки
            writer.WriteElementString("Name", name);
            // Путь к сборке
            writer.WriteElementString("Location", location);

            // Среда, где был запуск
            writer.WriteStartElement("Environment");
            writer.WriteElementString("System", Environment.OSVersion.VersionString);
            writer.WriteElementString("Runtime", Environment.Version.ToString());
            writer.WriteElementString("Machine", GetMachineInfo());
            writer.WriteEndElement();

            // Тесты
            foreach (Test test in tests)
            {
                writer.WriteStartElement("Test");
                writer.WriteAttributeString("Name", test.Name);
                writer.WriteAttributeString("Group", test.Group);

                foreach (SubtestResult result in test.Result.Subtests)
                {
                    writer.WriteStartElement("Inspection");
                    writer.WriteAttributeString("Name", result.Name);
                    writer.WriteElementString("Expected", result.Expected);
                    writer.WriteElementString("Actual", result.Actual);
                    writer.WriteElementString("ElapsedTime", result.ElapsedTime.ToString());

                    switch (result.Result)
                    {
                        case TestState.Failed: writer.WriteElementString("State", "Failed"); break;
                        case TestState.Successful: writer.WriteElementString("State", "Successful"); break;
                        case TestState.Untested: writer.WriteElementString("State", "Untested"); break;
                    }
                    
                    writer.WriteEndElement();
                }

                writer.WriteEndElement();
            }

            writer.WriteEndElement();
            writer.WriteEndDocument();

            writer.Close();
        }

        /// <summary>
        /// Получение краткой конфигурации 
        /// аппаратной части компьютера
        /// </summary>
        /// <returns></returns>
        string GetMachineInfo()
        {
            try
            {
                string cpuFrequency = "";
                string totalPhysicalMemory = "";
                string videoProcessor = "";
                string videoMemory = "";

                ManagementObjectSearcher searcher;
                ManagementObjectCollection connection;
                ObjectQuery query;
                

                ConnectionOptions options = new ConnectionOptions();
                ManagementScope scope = new ManagementScope("\\\\localhost\\root\\cimv2", options);

                // Получаем объем физической памяти
                query = new ObjectQuery("SELECT * FROM Win32_ComputerSystem");
                searcher = new ManagementObjectSearcher(scope, query);
                connection = searcher.Get();
                          
                foreach (ManagementObject mo in connection)
                {
                    totalPhysicalMemory = String.Format("{0:0}",
                        Int64.Parse(mo["totalphysicalmemory"].ToString()) / 1000000);
                }

                // Получаем информацию о процессоре
                query = new System.Management.ObjectQuery("SELECT * FROM Win32_processor");
                searcher = new ManagementObjectSearcher(scope, query);
                connection = searcher.Get();
               
                foreach (ManagementObject mo in connection)
                {   
                    cpuFrequency = String.Format("{0:0.0}", 
                        Double.Parse(mo["MaxClockSpeed"].ToString()) / 1000.0);
                }

                // Получаем информацию о видеокарте
                query = new System.Management.ObjectQuery("SELECT * FROM Win32_VideoController");
                searcher = new ManagementObjectSearcher(scope, query);
                connection = searcher.Get();
                                
                foreach (ManagementObject mo in connection)
                {
                    videoProcessor = mo["VideoProcessor"].ToString();
                    videoMemory = String.Format("{0:0}", 
                        Int32.Parse(mo["AdapterRAM"].ToString()) / 1000000);
                }

                return String.Format("{0}MHz/{1}Mb/{2} {3}Mb",
                    cpuFrequency,
                    totalPhysicalMemory,
                    videoProcessor,
                    videoMemory);

            }
            catch
            {
                // WMI штука такая, может и обламать :-|
                return "Неопределено";
            }
        }

        /// <summary>
        /// Запись результатов в файл.
        /// Формат XML, подробнее см. документацию
        /// </summary>>
        /// <param name="path">Путь к файлу</param>
        public void SaveReport(string path)
        {
            FileStream stream = File.Open(path, FileMode.Create);
            SaveReport(stream);
            stream.Close();
        }


        #endregion

        #region Статические методы

        /// <summary>
        /// Проверка на совместимость сборки
        /// </summary>
        /// <param name="assembly">Тестируемая сборка</param>
        /// <returns>Истина, если сборка совместима</returns>
        public static bool IsCompatibleAssembly(Assembly assembly)
        {
            Object[] attrs = assembly.GetCustomAttributes(typeof(DiagnosticAssemblyAttribute), true);
            return (attrs.Length != 0) ? true : false;
        }

        #endregion
    }
}

